package org.dubnation.car.service.impl;

import org.apache.commons.lang3.time.DateUtils;
import org.dubnation.base.config.HighlightResultMapper;
import org.dubnation.base.util.DocBucketVo;
import org.dubnation.base.util.PageInfo;
import org.dubnation.car.esdoc.CarDoc;
import org.dubnation.car.query.CarDocQuery;
import org.dubnation.car.repository.CarDocRepository;
import org.dubnation.car.service.ICarDocService;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class CarDocServiceImpl implements ICarDocService {

//    @Autowired
    private CarDocRepository carDocRepository;  //carDocRepository不支持做高亮

    @Autowired
    private PageInfo pageInfo;

    @Autowired
    private HighlightResultMapper highlightResultMapper;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;


    /**查询es中数据
     *   ElasticsearchRepository中findAll()和search()两种方法
     *   添加查询条件
     *     ①分页查询：用NativeSearchQueryBuilder中的withPageable()方法，
     *               该方法返回的对象.build()可以获取接口SearchQuery的子类NativeSearchQuery对象从而放到search()方法中
     *     ②DSL查询：
     *         组合查询：用NativeSearchQueryBuilder中的withQuery()方法
     *                 QueryBuilders.boolQuery()获取到的对象可以
     * @param carDocQuery
     * @return
     */
    @Override
    public PageInfo<CarDoc> queryPage(CarDocQuery carDocQuery) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //构建一个组合查询对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); //QueryBuilders可以获取QueryBuilder及其子类对象
        /**
         * DSL查询：类似于mysql的模糊查询，会进行分词匹配，计算相关度
         * DSL过滤：类似于mysql的等值查询筛选结果就是符合条件，会把结果集进行缓存用于后续的DSL查询
         *   模糊查询的效率较低，先匹配出数据范围再进行模糊查询可以提高查询的效率
         */
        /* ----------------DSL查询--------------- must: match,multi_match */
        /*
        * 模糊查询的分词匹配：1) QueryBuilders.matchQuery("要分词查询的字段", 关键字)
        *                 2) boolQuery.must(matchQuery)--将获取的MatchQueryBuilder对象添加到组合查询的分词查询中
        * */
        //标题搜索
        if (Objects.nonNull(carDocQuery.getKeyword()) && !carDocQuery.getKeyword().equals("")){
            MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", carDocQuery.getKeyword()); //title是要分词查询的字段
            boolQueryBuilder.must(matchQueryBuilder);
        }

        /* ----------------DSL过滤---------------filter:range, term */
        /*
        * 过滤查询的字段排序：1) QueryBuilders.rangeQuery("要排序的字段").gte(与索引库中字段比较的值)或.lte(与索引库中字段比较的值)
        *                 2) boolQuery.filter(rangeQuery)--将获取的RangeQueryBuilder对象添加到组合查询的过滤查询中
        * */
        //价格搜索
        if (Objects.nonNull(carDocQuery.getPriceMin())){
            //搜索所有售价大于carDocQuery.getPriceMin()的数据
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("costPrice").gte(carDocQuery.getPriceMin());
            boolQueryBuilder.filter(rangeQueryBuilder);
        }
        if (Objects.nonNull(carDocQuery.getPriceMax())){
            //搜索所有售价小于carDocQuery.getPriceMax()的数据
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("costPrice").lte(carDocQuery.getPriceMax());
            boolQueryBuilder.filter(rangeQueryBuilder);
        }
        //车龄搜索
        if (Objects.nonNull(carDocQuery.getCarAgeMin())){
            Long eddDate = DateUtils.addYears(new Date(), 0 - carDocQuery.getCarAgeMin()).getTime();
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("registerTime").lte(eddDate);
            boolQueryBuilder.filter(rangeQueryBuilder);
        }
        if (Objects.nonNull(carDocQuery.getCarAgeMax())){
            Long startDate = DateUtils.addYears(new Date(), 0 - carDocQuery.getCarAgeMax()).getTime();
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("registerTime").gte(startDate);
            boolQueryBuilder.filter(rangeQueryBuilder);
        }
        /*
        * 过滤查询的字段匹配：1) QueryBuilders.termQuery("匹配字段", 匹配字段需要查找的值)得到TermQueryBuilder
        *                 2) boolQuery.filter(termQuery)--将获得的TermQueryBuilder对象添加到组合查询的过滤查询中
        * */
        //急售、超值、准信、可迁全国搜索
        if (Objects.nonNull(carDocQuery.getCostEffective())){
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("costEffective", carDocQuery.getCostEffective());
            boolQueryBuilder.filter(termQueryBuilder);
        }
        if (Objects.nonNull(carDocQuery.getRushSale())){
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("rushSale", carDocQuery.getRushSale());
            boolQueryBuilder.filter(termQueryBuilder);
        }
        if (Objects.nonNull(carDocQuery.getQuasiNewCar())){
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("quasiNewCar", carDocQuery.getQuasiNewCar());
            boolQueryBuilder.filter(termQueryBuilder);
        }
        if (Objects.nonNull(carDocQuery.getTransitiveCountry())){
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("transitiveCountry", carDocQuery.getTransitiveCountry());
            boolQueryBuilder.filter(termQueryBuilder);
        }
        //店铺名称和品牌查询--搭配聚合查询
        if (Objects.nonNull(carDocQuery.getShopName())){
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("shopName", carDocQuery.getShopName());
            boolQueryBuilder.filter(termQueryBuilder);
        }
        if (Objects.nonNull(carDocQuery.getBrand())){
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brand", carDocQuery.getBrand());
            boolQueryBuilder.filter(termQueryBuilder);
        }
        //距离范围查询
        if (Objects.nonNull(carDocQuery.getLat()) && Objects.nonNull(carDocQuery.getLng()) && Objects.nonNull(carDocQuery.getDistance())){
            //1.距离范围的查询构建
            GeoDistanceQueryBuilder geoDistanceQueryBuilder = QueryBuilders.geoDistanceQuery("shopPoint");
            //2.把用户的经纬度设置到查询对象
            GeoDistanceQueryBuilder point = geoDistanceQueryBuilder.point(carDocQuery.getLat(), carDocQuery.getLng());
            //3.设置要查询的距离范围--1,2可以得到用户和店铺之间的距离
            point.distance(carDocQuery.getDistance(), DistanceUnit.KILOMETERS);
            boolQueryBuilder.filter(geoDistanceQueryBuilder);
        }
        /*-------------------添加排序条件---------------------*/
        // TODO withSort
        if (Objects.nonNull(carDocQuery.getOrderField()) && !carDocQuery.getOrderField().equals("")){
            queryBuilder.withSort(new FieldSortBuilder(carDocQuery.getOrderField())
                        .order(carDocQuery.getOrderType().equals("DESC")?SortOrder.DESC:SortOrder.ASC));
        }

        /*-------------------对关键字做高亮处理--------------*/
        HighlightBuilder.Field field = new HighlightBuilder.Field("title");
        field.preTags("<span style='color:red'>");
        field.postTags("</span>");
        // TODO withHighlightFields
        queryBuilder.withHighlightFields(field);

        /* ----------------添加聚合查询---------------
        * .term("设置桶的别名"), .filed("要分组的字段"), .size(分组字段的值出现的次数)
        * */
        TermsAggregationBuilder brandBuilder = AggregationBuilders.terms("brandBuckets").field("brand").size(30); //分组统计品牌字段的值出现的次数封装到一个bucket中
        TermsAggregationBuilder shopNameBuilder = AggregationBuilders.terms("shopNameBuckets").field("shopName").size(30);
        //将聚合查询返回的buckets添加到queryBuilder中
        // TODO addAggregation
        queryBuilder.addAggregation(brandBuilder);
        queryBuilder.addAggregation(shopNameBuilder);
        /* ----------------建立分页查询--------------- */
        //TODO withPageable
        queryBuilder.withPageable(PageRequest.of(carDocQuery.getCurrentPage()-1, carDocQuery.getPageSize()));
        //添加查询条件
        //TODO withQuery
        queryBuilder.withQuery(boolQueryBuilder);
        AggregatedPage<CarDoc> docPage = elasticsearchRestTemplate.queryForPage(queryBuilder.build(), CarDoc.class, highlightResultMapper);

        Aggregations aggregations = docPage.getAggregations();
        List<Aggregation> aggregationList = aggregations.asList();
        Map<String, List<DocBucketVo>> bucketMap = new HashMap<>();//PageInfo中用来存储各个字段桶的map
        /* 处理聚合结果集 */
        aggregationList.forEach(aggregation -> {
            String bucketName = aggregation.getName(); //每个字段对应的桶名字
            ParsedStringTerms parsedStringTerms = (ParsedStringTerms) aggregation;
            List<? extends Terms.Bucket> buckets = parsedStringTerms.getBuckets();
            List<DocBucketVo> bucketList = new ArrayList<>(); //存储docBucket
            buckets.forEach(bucket -> {
                DocBucketVo docBucket = new DocBucketVo();
                docBucket.setKey(bucket.getKeyAsString()); //key
                docBucket.setCount(bucket.getDocCount()); //count
                bucketList.add(docBucket);
            });
            bucketMap.put(bucketName, bucketList);
        });
        Long total = docPage.getTotalElements(); //命中的总条数
        List<CarDoc> data = docPage.getContent(); //命中的全部数据
        pageInfo.setTotal(total);
        pageInfo.setData(data);
        pageInfo.setAggs(bucketMap);
        return pageInfo;
    }
}
